// Arduino entry
#include <Arduino.h>
#include <HardwareSerial.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
#include <esp_system.h>
#include <math.h>

#include "gps.h"
#include "aprs.h"
#include "config.h"
#include "oled.h"
#include "radio.h"
// See AI_INSTRUCTIONS.txt for collaboration conventions.
// ----- GPS runtime moved to gps.* -----

// ===== IO mapping (confirmed) =====
// GPS (M210Z): RX1=GPIO7 (鈫怗PS TX), TX1=GPIO6 (鈫扜PS RX), ONOFF=GPIO5
// Radio audio: SPK_IN=GPIO4 (ADC), MIC_OUT=GPIO0 (AFSK PWM), PTT=GPIO2(+LED), SQL=GPIO10
// OLED I2C   : SDA=GPIO1, SCL=GPIO3
// User/LED   : KEY=GPIO9, GPS_LED=GPIO8
// Debug UART : U0RXD=GPIO20, U0TXD=GPIO21
// USB native : D-=GPIO18, D+=GPIO19

static const int PIN_SPK_IN   = 4;   // ADC input from radio speaker
static const int PIN_MIC_OUT  = 0;   // PWM/AFSK output to radio MIC
static const int PIN_PTT      = 2;   // PTT control
static const int PIN_SQL      = 10;  // Squelch detect
static const int PIN_KEY      = 9;   // User button
static const int PIN_LED_WIFI = -1;  // no dedicated WiFi LED
static const int PIN_LED_GPS  = 8;   // GPS status LED
static const int PIN_DBG_RX   = 20;  // Debug UART RX (U0RXD)
static const int PIN_DBG_TX   = 21;  // Debug UART TX (U0TXD)
static const int PIN_GPS_ONOFF= 5;   // GPS power/enable control
static const bool PIN_GPS_LED_ACTIVE_HIGH = false; // false = active-low LED

// GPS is managed by gps.* module
#define GPS_RX 7  // GPIO7 (GPS TXD -> RX1)
#define GPS_TX 6  // GPIO6 (GPS RXD -> TX1)


// gpsSetBaudByIndex moved to gps.*

// OLED wiring (I2C)
#define OLED_SCL 3
#define OLED_SDA 1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// Reserved slot for possible internal data structures (unused)
#if 0 // UNUSED: placeholder for legacy OLED experiments
#endif



// Values are loaded from NVS via config.*
#if 0
const char* ssid = "BD3ND_J";
const char* password = "12345678abc";
#endif

#if 0
const char* callsign = "BD3ND-10";
const char* aprsServer = "rotate.aprs.net";
const uint16_t aprsPort = 14580;
const char symTable = "/"[0];
const char symChar  = ">"[0];

#endif
WiFiClient client;
// uint16_t passcodeNum = 0; // kept for inspecting aprs_calculatePasscode()
unsigned long lastSend = 0;
unsigned long sendIntervalMs = 5UL * 60UL * 1000UL; // updated in setup
unsigned long lastDebug = 0;
unsigned long lastRxPrint = 0;
static bool prevGpsFix = false;
static bool wifiLedOn = false;

// Forward decls
void handleUsbCli();
void connectToWiFiCfg();
// GPS/APRS helpers moved into modules

// GPS ON/OFF helpers provided by gps.*

// Print last reset reason on boot
void printResetInfo() {
  esp_reset_reason_t reason = esp_reset_reason();
  const char* desc = u8"未知";
  switch (reason) {
    case ESP_RST_POWERON:  desc = u8"上电"; break;
    case ESP_RST_SW:       desc = u8"软件复位"; break;
    case ESP_RST_PANIC:    desc = u8"Panic"; break;
    case ESP_RST_INT_WDT:  desc = u8"中断 WDT"; break;
    case ESP_RST_TASK_WDT: desc = u8"任务 WDT"; break;
    case ESP_RST_WDT:      desc = u8"WDT"; break;
    case ESP_RST_DEEPSLEEP:desc = u8"深度睡眠唤醒"; break;
    case ESP_RST_BROWNOUT: desc = u8"欠压"; break;
    case ESP_RST_SDIO:     desc = u8"SDIO"; break;
    default: break;
  }
  Serial.printf(u8"[复位信息] %s (%d)\n", desc, (int)reason);
}


// UNUSED: legacy connectToWiFi(); config-driven flow now uses connectToWiFiCfg()
#if 0
void connectToWiFi() {
  Serial.print("[WiFi] Connecting: "); Serial.println(ssid);
  WiFi.begin(ssid, password);
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 20) {
    delay(500);
    Serial.print(".");
    retry++;
  }
  if (WiFi.status() == WL_CONNECTED) {
  } else {
  }
}
#endif
// APRS-IS passcode example (use aprs_calculatePasscode())
uint16_t calculatePasscode(const char* call) {
  String root = String(call);
  int dash = root.indexOf('-');
  if (dash > 0) root = root.substring(0, dash);
  root.toUpperCase();
  uint8_t hi = 0x73, lo = 0xE2;
  size_t len = root.length();
  for (size_t i = 0; i < len; i += 2) {
    uint8_t c1 = root.charAt(i);
    uint8_t c2 = (i + 1 < len) ? root.charAt(i + 1) : 0;
    hi ^= c1;
    lo ^= c2;
  }
  return (uint16_t)(((hi & 0x7F) << 8) | lo);
}
// config helpers moved to config.*

// Mirror config output in ASCII for GUI compatibility
static void printConfigAscii() {
  String status = cfg.statusText;
  status.replace('\r', ' ');
  status.replace('\n', ' ');
  status.trim();
  Serial.printf("callsign: %s (pass: %u)\n", cfg.callsign.c_str(), aprs_calculatePasscode(cfg.callsign.c_str()));
  Serial.printf("APRS-IS : %s:%u\n", cfg.aprsServer.c_str(), cfg.aprsPort);
  Serial.printf("WiFi    : %s / %s\n", cfg.wifiSsid.c_str(), cfg.wifiPass.c_str());
  Serial.printf("Symbol  : %c %c\n", cfg.symTable, cfg.symChar);
  Serial.printf("Beacon  : %u sec\n", cfg.beaconIntervalSec);
  Serial.printf("Status  : %s\n", status.c_str());
}

void handleUsbCli() {
  static String line;
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r') continue;
    if (c == '\n') {
      line.trim();
      if (line.length() == 0) { line = ""; continue; }
      if (line.equalsIgnoreCase("help")) {
        Serial.println(F("Commands: help, show, set <...>, save, reboot, id, ping"));
      } else if (line.equalsIgnoreCase("id") || line.equalsIgnoreCase("ping") || line.equals("?")) {
        // Respond to simple probes from PC tools (ASCII friendly)
        Serial.println(F("[ID] ESP32_APRS"));
      } else if (line.equalsIgnoreCase("show")) {
        printConfigAscii();
        printConfig();
      } else if (line.startsWith("set ")) {
        int sp1 = line.indexOf(' ');
        int sp2 = line.indexOf(' ', sp1 + 1);
        String key = line.substring(sp1 + 1, sp2);
        if (key.equalsIgnoreCase("CALLSIGN")) {
          String v = line.substring(sp2 + 1);
          v.trim(); if (v.length() > 0) cfg.callsign = v;
        } else if (key.equalsIgnoreCase("WIFI")) {
          int sp3 = line.indexOf(' ', sp2 + 1);
          if (sp3 > 0) {
            cfg.wifiSsid = line.substring(sp2 + 1, sp3);
            cfg.wifiPass = line.substring(sp3 + 1);
          } else {
          }
        } else if (key.equalsIgnoreCase("APRS")) {
          int sp3 = line.indexOf(' ', sp2 + 1);
          if (sp3 > 0) {
            cfg.aprsServer = line.substring(sp2 + 1, sp3);
            cfg.aprsPort = (uint16_t)line.substring(sp3 + 1).toInt();
          } else {
          }
        } else if (key.equalsIgnoreCase("SYMBOL")) {
          int sp3 = line.indexOf(' ', sp2 + 1);
          if (sp3 > 0 && (line[sp2 + 1] == '/' || line[sp2 + 1] == '\\')) {
            cfg.symTable = line[sp2 + 1];
            String cstr = line.substring(sp3 + 1); cstr.trim();
            if (cstr.length() > 0) cfg.symChar = cstr[0];
          } else {
          }
        } else if (key.equalsIgnoreCase("COMMENT")) {
          String v = line.substring(sp2 + 1); v.trim();
          if (v.length() > 0) { cfg.statusText = v; Serial.printf("[CFG] comment set: %s\n", cfg.statusText.c_str()); }
        } else if (key.equalsIgnoreCase("INTERVAL")) {
          String v = line.substring(sp2 + 1); v.trim();
          unsigned long sec = (unsigned long)v.toInt();
          if (sec > 0) { cfg.beaconIntervalSec = (uint16_t)sec; sendIntervalMs = sec * 1000UL; lastSend = 0; Serial.printf("[CFG] beacon interval=%lus\n", sec); }
        } else {
        }
      } else if (line.startsWith("gps ")) {
        int sp1 = line.indexOf(' ');
        int sp2 = line.indexOf(' ', sp1 + 1);
        String sub = (sp2>0) ? line.substring(sp1+1, sp2) : line.substring(sp1+1);
        sub.trim(); sub.toLowerCase();
        if (sub == "debug") {
          String arg = (sp2>0) ? line.substring(sp2+1) : ""; arg.trim(); arg.toLowerCase();
          bool on = !(arg == "off" || arg == "0");
          gpsSetDebug(on);
          Serial.printf("[GPS] debug=%s\n", on?"on":"off");
        } else if (sub == "raw") {
          String arg = (sp2>0) ? line.substring(sp2+1) : ""; arg.trim(); arg.toLowerCase();
          bool on = !(arg == "off" || arg == "0");
          gpsSetRaw(on);
          Serial.printf("[GPS] raw echo=%s (warn: very verbose)\n", on?"on":"off");
        } else if (sub == "on") {
          gpsPowerOn(); Serial.println("[GPS] ONOFF=HIGH (enabled)");
        } else if (sub == "off") {
          gpsPowerOff(); Serial.println("[GPS] ONOFF=LOW (disabled)");
        } else if (sub == "reset") {
          gpsPowerOff(); delay(200); gpsPowerOn();
        } else if (sub == "send") {
          // Send raw string to GPS (append CRLF if not present)
          String payload = (sp2>0) ? line.substring(sp2+1) : "";
          if (payload.length()==0) { Serial.println(F("用法: gps send <text>")); }
          else { gpsSendRaw(payload); Serial.print("[GPS] sent raw: "); Serial.print(payload); }
        } else if (sub == "nmea") {
          // Build NMEA with checksum automatically from payload (without '$')
          String payload = (sp2>0) ? line.substring(sp2+1) : ""; payload.trim();
          if (payload.length()==0) { Serial.println(F("用法: gps nmea <payload-no-$>")); }
          else { gpsSendNmea(payload); Serial.print("[GPS] sent NMEA payload: "); Serial.println(payload); }
        } else if (sub == "hex") {
          // Send hex bytes to GPS, e.g. "gps hex B5 62 06 08 00 00 0E 30"
          String rest = (sp2>0) ? line.substring(sp2+1) : ""; rest.trim();
          if (rest.length()==0) { Serial.println(F("用法: gps hex <byte1> <byte2> ... (十六进制)")); }
          else { gpsSendHexStr(rest); Serial.println("[GPS] sent HEX sequence"); }
        } else if (sub == "baud") {
          String arg = (sp2>0) ? line.substring(sp2+1) : ""; arg.trim();
          int br = arg.toInt(); if (br<=0) br = 9600;
          gpsSetBaud(br);
        } else if (sub == "scan") {
          gpsScanBaud();
        } else {
        }
      } else if (line.equalsIgnoreCase("save")) {
        saveConfig();
      } else if (line.equalsIgnoreCase("reboot")) {
        delay(100);
        ESP.restart();
      } else {
      }
      line = "";
    } else {
      line += c;
    }
  }
}

String aprsLat(float lat) { return aprs_formatLat(lat); }
String aprsLon(float lon) { return aprs_formatLon(lon); }

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("[System] Init serial");
  printResetInfo();

  // IO init
  pinMode(PIN_PTT, OUTPUT); digitalWrite(PIN_PTT, LOW);
  pinMode(PIN_SQL, INPUT);
  pinMode(PIN_KEY, INPUT_PULLUP);
  if (PIN_LED_WIFI >= 0) { pinMode(PIN_LED_WIFI, OUTPUT); digitalWrite(PIN_LED_WIFI, LOW); }
  pinMode(PIN_LED_GPS, OUTPUT);  digitalWrite(PIN_LED_GPS, LOW);
  // Keep GPS enabled by default (active-high ON)
  gpsPowerOn();
  Serial.println("[GPS] ONOFF pin set HIGH (active)");

  // Load persistent config
  loadConfig();
  if (cfg.callsign == "NOCALL-10") {
    Serial.println("[Info] First run, use USB CLI: help");
  }
  // Ensure interval defaults to 5 minutes on first run
  if (cfg.beaconIntervalSec == 0) cfg.beaconIntervalSec = 300;
  sendIntervalMs = (unsigned long)cfg.beaconIntervalSec * 1000UL;

  
  gpsInit(GPS_RX, GPS_TX, PIN_GPS_ONOFF, PIN_LED_GPS, true, 115200);
  Serial.println("[System] GPS UART started");
  // delay after gps init


  // Initialize the OLED UI (multi-page layout)
  oledInit(OLED_SDA, OLED_SCL);
  // Startup steps on OLED
  oledStartupStep(F("Load config..."), 1, 5);
  delay(150);
  oledStartupStep(F("Init GPS..."), 2, 5);
  delay(150);
  oledStartupStep(F("Connect WiFi..."), 3, 5);
  delay(150);

  if (digitalRead(PIN_KEY) == LOW) {
    Serial.println("[SafeMode] KEY held: skip WiFi");
  } else {
    connectToWiFiCfg();
  }
  oledStartupStep(F("Prepare APRS..."), 4, 5);
  delay(150);
  oledStartupStep(F("Ready"), 5, 5);
  delay(400);
  oledUpdate(cfg, PIN_KEY, PIN_SQL, PIN_PTT);
}
void loop() {
  gpsHandle();
  if (millis() - lastSend >= sendIntervalMs && gpsHasFix()) {
    if (aprs_sendPosition(client,
                          cfg.callsign.c_str(),
                          cfg.aprsServer.c_str(),
                          cfg.aprsPort,
                          cfg.symTable,
                          cfg.symChar,
                          aprs_calculatePasscode(cfg.callsign.c_str()),
                          gpsLatitude(),
                          gpsLongitude(),
                          cfg.statusText.c_str()))
      lastSend = millis();
  }
  handleUsbCli();
  oledUpdate(cfg, PIN_KEY, PIN_SQL, PIN_PTT);
  if (millis() - lastDebug > 10000) {
    lastDebug = millis();
    Serial.printf("[State] GPS: lat=%.5f lon=%.5f\n", gpsLatitude(), gpsLongitude());
  }
  // OLED UI refresh is handled by oledUpdate()
}

void connectToWiFiCfg() {
  Serial.print("[WiFi] Connecting: "); Serial.println(cfg.wifiSsid);
  WiFi.persistent(false);
  WiFi.disconnect(true, true);
  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(true);
  // lower TX power to reduce inrush / supply dip
  WiFi.setTxPower(WIFI_POWER_8_5dBm);
  delay(200);

  WiFi.begin(cfg.wifiSsid.c_str(), cfg.wifiPass.c_str());
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 40) {
    delay(250);
    Serial.print(".");
    retry++;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\n[WiFi] Connected: " + WiFi.localIP().toString());
    if (PIN_LED_WIFI >= 0) digitalWrite(PIN_LED_WIFI, HIGH);
  } else {
    Serial.println("\n[WiFi] Connect failed");
    if (PIN_LED_WIFI >= 0) digitalWrite(PIN_LED_WIFI, LOW);
  }
}



